/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: gif.c,v 1.3 2005/07/12 23:12:29 pullmoll Exp $
 *****************************************************************************/
#include "gif.h"
#include "tools.h"
#include "logger.h"

#define	FONTNAME	"node/font8x13.txt"

uint8_t *font8x13 = NULL;

#define GIF_MAXSTR 255
#define GIF_BUFFER 384

int interlace_step[5] = {7, 7, 3, 1, 0};
int interlace_start[5] = {0, 4, 2, 1, 0};

#define	POW2(n)	(2u << (n))

/* FILE pointer reader */
static INLINE int gif_read_fp(void *user, void *buff, size_t size)
{
	FILE *fp = (FILE *)user;
	return fread(buff, 1, size, fp);
}

/* FILE pointer writer */
static INLINE int gif_write_fp(void *user, const void *buff, size_t size)
{
	FILE *fp = (FILE *)user;
	return fwrite(buff, 1, size, fp);
}

/* get a character from the GIF input stream */
static INLINE int gif_getchar(gif_t *gif)
{
	uint8_t buff[1];
	int rc;
	if (NULL == gif || NULL == gif->input) {
		errno = EINVAL;
		return -1;
	}
	rc = (*gif->input)(gif->user, &buff, 1);	
	return (-1 == rc) ? rc : buff[0];
}

/* put a character to the GIF output stream */
static INLINE int gif_putchar(gif_t *gif, int ch)
{
	uint8_t buff[1];
	if (NULL == gif || NULL == gif->output) {
		errno = EINVAL;
		return -1;
	}
	buff[0] = (uint8_t)ch;
	return (*gif->output)(gif->user, buff, 1);
}

/* put an unsigned short to the GIF output stream */
static INLINE int gif_putshort(gif_t *gif, int sh)
{
	uint8_t buff[2];
	if (NULL == gif || NULL == gif->output) {
		errno = EINVAL;
		return -1;
	}
	buff[0] = (uint8_t)(sh % 256);
	buff[1] = (uint8_t)(sh / 256);
	return (*gif->output)(gif->user, buff, 2);
}

/* put an unsigned short from the GIF input stream */
static INLINE int gif_getshort(gif_t *gif)
{
	uint8_t buff[2];
	int rc;

	if (NULL == gif || NULL == gif->input) {
		errno = EINVAL;
		return -1;
	}
	rc = (*gif->input)(gif->user, buff, 2);
	return (-1 == rc) ? rc : 256 * buff[1] + buff[0];
}

/* get a buffer of 'size' bytes from the GIF input stream */
static INLINE int gif_getbuff(gif_t *gif, void *buff, size_t size)
{
	if (NULL == gif || NULL == gif->input) {
		errno = EINVAL;
		return -1;
	}
	return (*gif->input)(gif->user, buff, size);
}

/* put a buffer of 'size' bytes to the GIF output stream */
static INLINE int gif_putbuff(gif_t *gif, const void *buff, size_t size)
{
	if (NULL == gif || NULL == gif->output) {
		errno = EINVAL;
		return -1;
	}
	return (*gif->output)(gif->user, buff, size);
}

/* encode a GIF picture at 'pix', bit depth 'depth' and size 'siz' */
void gif_encode(gif_t *gif, uint8_t *pix, int depth, int siz)
{
	gif_encoder_t *enc = &gif->enc;
	gif_tree_t *first = &enc->root;
	gif_tree_t *new_node;
	gif_tree_t *cur_node;
	uint8_t *end;
	int cur_code, eoi_code, nxt_code, tel = 0;
	uint32_t code_length;
	char *pos;
	FUN("gif_encode");

	enc->root.type = NODE_LOOKUP;
	enc->root.ix = 0;
	enc->root.code = 0;
	enc->root.node = enc->empty;
	enc->root.next = NULL;
	enc->root.alt = NULL;

	enc->need = 8;

	enc->empty[0] = NULL;
	memmove(&enc->empty[1], &enc->empty[0],
		255 * sizeof(gif_tree_t **));

	pos = &enc->buff[1];
	enc->buff[0] = 0x0;
	enc->buff[1] = 0x0;

	gif_putchar(gif, (depth == 1) ? 2 : depth);
	cur_code = (depth == 1) ? 4 : 1 << depth;
	eoi_code = cur_code + 1;
	nxt_code = cur_code + 2;
	code_length = (depth == 1) ? 3 : depth + 1;

	enc->top_node = enc->base_node =
		(gif_tree_t *)xcalloc(4094, sizeof(gif_tree_t));
	enc->node_array = first->node =
		(gif_tree_t **)xcalloc(NODE_ARRAY_MAX * 256, sizeof(gif_tree_t *));
	enc->last_array = enc->node_array + (256 * NODE_ARRAY_MAX - cur_code);
	gif_clear_tree(gif, cur_code, first);

	pos = gif_add_code(gif, cur_code, code_length, pos);

	end = pix + siz;
	cur_node = first;
	while (pix < end) {

		if (NULL != cur_node->node[*pix]) {
			cur_node = cur_node->node[*pix];
			tel++;
			pix++;
			enc->chainlen++;
			continue;
		} else if (NODE_SEARCH == cur_node->type) {
			new_node = cur_node->next;
			while (NULL != new_node->alt) {
				if (*pix == new_node->ix)
					break;
				new_node = new_node->alt;
			}
			if (*pix == new_node->ix) {
				tel++;
				pix++;
				enc->chainlen++;
				cur_node = new_node;
				continue;
			}
		}
/*******************************************************
 * If there is no more thread to follow, we create a
 * new node. If the current node is terminating, it
 * will become a NODE_SEARCH node.  If it is a
 * NODE_SEARCH node, and if we still have room, it
 * will be converted to a NODE_LOOKUP node.
 *******************************************************/
		new_node = ++enc->top_node;
		switch (cur_node->type) {
		case NODE_LOOKUP:
			new_node->next = NULL;
			new_node->alt = NULL;
			cur_node->node[*pix] = new_node;
			break;
		case NODE_SEARCH:
			if (enc->node_array != enc->last_array) {
				enc->node_array += cur_code;
				cur_node->node = enc->node_array;
				cur_node->type = NODE_LOOKUP;
				cur_node->node[*pix] = new_node;
				cur_node->node[(cur_node->next)->ix] = cur_node->next;
				enc->node_lookups++;
				new_node->next = NULL;
				new_node->alt = NULL;
				cur_node->next = NULL;
				break;
			}
/* otherwise do as we do with a TERMIN node  */
		case NODE_TERMIN:
			new_node->alt = cur_node->next;
			new_node->next = NULL;
			cur_node->next = new_node;
			cur_node->type = NODE_SEARCH;
			break;
		default:
			LOG(L_ERROR,("bad node type:%d\n",
				cur_node->type));
		}
		new_node->code = nxt_code;
		new_node->ix = *pix;
		new_node->type = NODE_TERMIN;
		new_node->node = enc->empty;
		enc->node_count++;
/********************************************************
 * End of node creation
 ********************************************************/
		if (cur_node == new_node) {
			LOG(L_ERROR,("wrong choice of node\n"));
		}
		if (NODE_LOOKUP == cur_node->type &&
			new_node != cur_node->node[*pix]) {
			LOG(L_ERROR,("wrong pixel coding\n"));
		}
		if (NODE_TERMIN == cur_node->type) {
			LOG(L_ERROR,("wrong type; frame no:%d, pixel #%d, node_count:%d\n",
				gif->frame_count, tel, enc->node_count));
		}
		pos = gif_add_code(gif, cur_node->code, code_length, pos);
		if (enc->chainlen > enc->maxchainlen)
			enc->maxchainlen = enc->chainlen;
		enc->chainlen = 0;
		if (pos - enc->buff > GIF_MAXSTR) {
			enc->buff[0] = (char)GIF_MAXSTR;
			gif_putbuff(gif, enc->buff, GIF_MAXSTR + 1);
			enc->buff[1] = enc->buff[GIF_MAXSTR + 1];
			enc->buff[2] = enc->buff[GIF_MAXSTR + 2];
			enc->buff[3] = enc->buff[GIF_MAXSTR + 3];
			enc->buff[4] = enc->buff[GIF_MAXSTR + 4];
			pos -= GIF_MAXSTR;
		}
		cur_node = first;

		if (nxt_code == (1 << code_length))
			code_length++;
		nxt_code++;

		if (nxt_code == 0xfff) {
			gif_clear_tree(gif, cur_code, first);
			pos = gif_add_code(gif, cur_code, code_length, pos);
			if (pos - enc->buff >= GIF_MAXSTR) {
				enc->buff[0] = (char)GIF_MAXSTR;
				gif_putbuff(gif, enc->buff, GIF_MAXSTR + 1);
				enc->buff[1] = enc->buff[GIF_MAXSTR + 1];
				enc->buff[2] = enc->buff[GIF_MAXSTR + 2];
				enc->buff[3] = enc->buff[GIF_MAXSTR + 3];
				enc->buff[4] = enc->buff[GIF_MAXSTR + 4];
				pos -= GIF_MAXSTR;
			}
			nxt_code = cur_code + 2;
			code_length = (depth == 1) ? 3 : depth + 1;
		}
	}

	pos = gif_add_code(gif, cur_node->code, code_length, pos);
	if (pos - enc->buff > GIF_MAXSTR - 3) {
		enc->buff[0] = (char)(GIF_MAXSTR - 3);
		gif_putbuff(gif, enc->buff, GIF_MAXSTR - 2);
		enc->buff[1] = enc->buff[GIF_MAXSTR - 2];
		enc->buff[2] = enc->buff[GIF_MAXSTR - 1];
		enc->buff[3] = enc->buff[GIF_MAXSTR - 0];
		enc->buff[4] = enc->buff[GIF_MAXSTR + 1];
		enc->buff[5] = enc->buff[GIF_MAXSTR + 2];
		pos -= GIF_MAXSTR - 3;
	}
	pos = gif_add_code(gif, eoi_code, code_length, pos);
	pos = gif_add_code(gif, 0x0, -1, pos);
	enc->buff[0] = pos - &enc->buff[1];

	gif_putbuff(gif, enc->buff, pos - enc->buff);
	xfree(first->node);
	xfree(enc->base_node);

	LOG(L_DEBUG,("pixel count:%d, node_count:%d, node lookups:%d\n",
		tel, enc->node_count, enc->node_lookups));
	return;
}

/* clear the encoder's LZW search tree */
void gif_clear_tree(gif_t *gif, int cur_code, gif_tree_t *root)
{
	gif_encoder_t *enc = &gif->enc;
	gif_tree_t *new_node, **xx;
	int i;
	FUN("gif_clear_tree");

	LOG(L_DEBUG,("clear tree cur_code:%d, nodecout:%d, lookup nodes:%d\n",
		cur_code, enc->node_count, enc->node_lookups));

	enc->maxchainlen = 0;
	enc->node_lookups = 1;
	enc->node_count = 0;
	enc->node_array = root->node;
	xx = enc->node_array;
	for (i = 0; i < NODE_ARRAY_MAX; i++) {
		memmove(xx, enc->empty, 256 * sizeof(gif_tree_t **));
		xx += 256;
	}
	enc->top_node = enc->base_node;
	for (i = 0; i < cur_code; i++) {
		root->node[i] = new_node = ++enc->top_node;
		new_node->next = NULL;
		new_node->alt = NULL;
		new_node->code = i;
		new_node->ix = i;
		new_node->type = NODE_TERMIN;
		new_node->node = enc->empty;
		enc->node_count++;
	}
}

/* add a code to the output stream; encode full octets into 'buf' */
char *gif_add_code(gif_t *gif, int code, int n, char *buf)
{
	gif_encoder_t *enc = &gif->enc;
	if (n < 0) {
		if (enc->need < 8) {
			buf++;
			*buf = 0x0;
		}
		enc->need = 8;
		return buf;
	}
	while ((unsigned)n >= enc->need) {
		enc->mask = (1 << enc->need) - 1;
		*buf |= (enc->mask & code) << (8 - enc->need);
		buf++;
		*buf = 0x0;
		code >>= enc->need;
		n -= enc->need;
		enc->need = 8;
	}
	if (n > 0) {
		enc->mask = (1 << n) - 1;
		*buf |= (enc->mask & code) << (8 - enc->need);
		enc->need -= n;
	}
	return buf;
}

/* decode a GIF LZW stream into pixels in the buffer at 'gif->picture' */
void gif_decode(gif_t *gif)
{
	gif_decoder_t *dec = &gif->dec;
	uint8_t *pix, *ch, out_code = 0, gif_str[256];
	uint32_t clr_code, eoi_code, bits = 0, code = 0;
	uint32_t code_fetched;
	int buff_count = 0;
	FUN("gif_decode");

	pix = gif->picture;
	dec->need = 0;
	dec->offs = 0;
	dec->size = gif->img.width * gif->img.height;
	dec->pass = 0;

	LOG(L_MINOR,("interlace:%d, img.width:%d, img.height:%d, size:%d\n",
		gif->img.interlace, gif->img.width, gif->img.height, dec->size));

	dec->code_root = gif_getchar(gif);
	clr_code = 1 << dec->code_root;
	eoi_code = clr_code + 1;

	gif_clear_table(gif);

	if (0 == (buff_count = gif_getchar(gif))) {
		LOG(L_ERROR,("end of image #%d before it began\n",
			gif->frame_count));
		return;
	}

	while (buff_count > 0) {
		if (buff_count != gif_getbuff(gif, gif_str, buff_count)) {
			LOG(L_ERROR,("premature end of file; image #%d\n",
				gif->frame_count));
			return;
		}
		for (ch = gif_str; buff_count-- > 0; ch++) {
			dec->need |= (int)*ch << bits;
			bits += 8;
			while (bits >= dec->code_size) {
				code = dec->need & dec->mask;
				dec->need >>= dec->code_size;
				bits -= dec->code_size;
				if (code > dec->expected) {
					LOG(L_ERROR,("neither LZW nor RunLength\n"));
					return;
				} else if (code == eoi_code) {
					LOG(L_DEBUG,("image #%d ends; buff_count:%d\n",
						gif->frame_count, buff_count));
					goto bailout;
				} else if (code == clr_code) {
					gif_clear_table(gif);
					continue;
				} else if (dec->code_old == LZW_MAXVAL) {
					/* i.e. first code after clear table */
					pix = gif_send_data(gif, pix, 1, &dec->last[code]);
					out_code = dec->last[code];
					dec->code_old = code;
					continue;
				}
				code_fetched = code;
				if (code == dec->expected) {
					*dec->top_code++ = out_code;
					code = dec->code_old;
				}
				while (code > clr_code) {
					*dec->top_code++ = dec->last[code];
					code = dec->first[code];
				}

				*dec->top_code++ = out_code = dec->last[code];
				dec->first[dec->expected] = dec->code_old;
				dec->last[dec->expected] = out_code;
				if (dec->expected < LZW_MAXVAL)
					dec->expected++;
				if (0 == (dec->expected & dec->mask) &&
					dec->expected < LZW_MAXVAL) {
					dec->code_size++;
					dec->mask += dec->expected;
				}
				dec->code_old = code_fetched;
				pix = gif_send_data(gif, pix,
					dec->top_code - dec->code, dec->code);
				dec->top_code = dec->code;

			}	/* end of extracting codes */
		}	/* end of reading a block of data */

		if (0 == (buff_count = gif_getchar(gif))) {
			LOG(L_ERROR,("end of image #%d w/o eoi_code\n",
				gif->frame_count));
			return;
		}
	}

bailout:
	LOG(L_DEBUG,("ending gif_decode, written: %d=%d\n",
		(int)(gif->img.interlace && (0 == pix - gif->picture) ?
			dec->size : pix - gif->picture),
		(int)dec->size));
	return;
}

/* send a decoded chain of pixels to the current gif->picture[offs++] */
uint8_t *gif_send_data(gif_t *gif, uint8_t *pix, int bytes, uint8_t *src)
{
	gif_decoder_t *dec = &gif->dec;
	int j = 0;
	FUN("gif_send_data");

	for (j = bytes - 1; j >= 0; j--) {
		*pix++ = src[j];
		dec->offs++;
		if (gif->img.interlace && (0 == dec->offs % gif->img.width)) {
			dec->offs += (gif->img.width * interlace_step[dec->pass]);
			if (dec->offs >= dec->size) {
				dec->offs = interlace_start[++dec->pass] * gif->img.width;
				LOG(L_DEBUG,("de-interlacing: offs:%d, start[%d]:%d\n",
					dec->offs,
					dec->pass,
					interlace_start[dec->pass]));
			}
			pix = &gif->picture[dec->offs];
		}
	}

	return pix;
}

/* clear the decoder's LZW table (at the start or after a reset code) */
void gif_clear_table(gif_t *gif)
{
	gif_decoder_t *dec = &gif->dec;
	int i, size;
	FUN("gif_clear_table");

	size = 1 << dec->code_root;
	dec->expected = size + 2;
	LOG(L_DEBUG,("initing table..."));

	dec->code_old = LZW_MAXVAL;
	dec->code_size = dec->code_root + 1;
	dec->mask = (1 << dec->code_size) - 1;

	for (i = 0; i < size; i++) {
		dec->first[i] = LZW_MAXVAL;
		dec->last[i] = i & 0xff;
	}
	dec->top_code = dec->code;
}

/* Pythagoras */
static INLINE long sq(uint8_t a, uint8_t b)
{
	return (a-b) * (a-b);
}

/* append an image from a GIF file to the GIF output stream 'gif_out' */
int gif_append_file(gif_t *gif_out, const char *filename, int firstimage)
{
	gif_t *gif_in;
	FILE *fp;
	uint32_t i;
	FUN("gif_append_file");

	gif_in = (gif_t *)xcalloc(1, sizeof(gif_t));

	if (NULL == (fp = fopen(filename, "rb"))) {
		LOG(L_ERROR,("fopen('%s','%s') call failed (%s)\n",
			filename, "rb", strerror(errno)));
		return -1;
	}
	gif_in->user = fp;
	gif_in->input = gif_read_fp;

	gif_read_screen_header(gif_in, gif_out, firstimage);

	for (;;) {
		i = gif_getchar(gif_in);
		switch (i) {
		case ',':
		case '\0':
			break;
		case '!':
			/* skip gif extension(s) */
			for (i = gif_getchar(gif_in); i > 0; i--)
				gif_getchar(gif_in);
			while ((i = gif_getchar(gif_in)) > 0) {
				for (/* */; i > 0; i--)
					gif_getchar(gif_in);
			}
			break;
		case ';':
			LOG(L_ERROR,("unexpected end-of-file\n"));
			fclose(fp);
			return -1;
		default:
			fclose(fp);
			LOG(L_ERROR,("unexpected block type (%c)\n",
				i));
			return -1;
		}

		if (i == ',')
			break;
	}

	if (0 != firstimage) {
		gif_out->scr.colormap = gif_in->scr.colormap;
		gif_out->scr.bpp = gif_in->scr.bpp;
		gif_out->scr.bc = gif_in->scr.bc;
		if (0 != gif_in->scr.colormap) {
			for (i = 0; i < POW2(gif_in->scr.bpp); i++) {
				gif_in->gmap[i].cmap.red = gif_in->cmap[i].cmap.red;
				gif_in->gmap[i].cmap.green = gif_in->cmap[i].cmap.green;
				gif_in->gmap[i].cmap.blue = gif_in->cmap[i].cmap.blue;
			}
		}

		if (gif_out->loop > 0) {
			gif_loop(gif_out, gif_out->loop);
		}
	}

	gif_read_image_header(gif_in);

	if (0 != gif_in->img.colormap && 0 != gif_out->img.colormap) {
		uint8_t xlt[256], *pi, *po;
		int top = 0, left = 0, bottom = 0, right = 0;
		int x, y, offs, l, h, w;
		uint32_t i, j;
		long int dsqmin, dsqcur;

		w = gif_in->img.width;
		h = gif_in->img.height;
		gif_in->picture = (uint8_t *)xcalloc(w * h, sizeof(uint8_t));
		gif_decode(gif_in);

		/* img is no longer interlaced */
		gif_in->img.interlace = 0;

		/* find best matching colors */
		for (j = 0; j < POW2(gif_in->scr.bpp); j++) {
			dsqmin = 256 * 256 * 3;
			for (i = 0; i < POW2(gif_out->scr.bpp); i++) {
				dsqcur =
					sq(gif_in->gmap[i].cmap.red, gif_in->cmap[i].cmap.red) +
					sq(gif_in->gmap[i].cmap.green, gif_in->cmap[i].cmap.green) +
					sq(gif_in->gmap[i].cmap.blue, gif_in->cmap[i].cmap.blue);
				if (dsqcur < dsqmin) {
					dsqmin = dsqcur;
					xlt[j] = i;
					if (0 == dsqmin)
						break;
				}
			}
		}

		/* img does no longer have a colormap */
		gif_in->img.colormap = 0;
		gif_in->scr.bpp = gif_out->scr.bpp;
		LOG(L_MINOR,("translating gif #%d\n", gif_out->frame_count));
		for (l = 0; l < w * h; l++)
			gif_in->picture[l] = xlt[gif_in->picture[l]];
		if (NULL != gif_out->picture &&
			w == gif_out->old.img.width &&
			h == gif_out->old.img.height &&
			gif_in->img.top == gif_out->old.img.top &&
			gif_in->img.left == gif_out->old.img.left) {

			gif_out->old.img = gif_in->img;
			pi = gif_in->picture;
			po = gif_out->picture;
			for (y = 0; y < h; y++) {
				for (x = 0; *pi == *po && x < w; x++) {
					pi++;
					po++;
				}
				/* row did not match? */
				if (x != w) {
					top = y;
					left = x;
					break;
				}
			}

			/* image did not match? */
			if (y != h) {
				offs = w * h - 1;
				pi = &gif_in->picture[offs];
				po = &gif_out->picture[offs];
				for (y = h - 1; *pi == *po && y >= top; y--) {
					for (x = w - 1; *pi == *po && x >= 0; x--) {
						pi--;
						po--;
					}
					if (x >= 0) {
						bottom = y + 1 < h - 1 ? y + 1 : y;
						right = x + 1 < w - 1 ? x + 1 : x;
					}
				}

				/* the outline of the difference may be slanted */
				if (right < left) {
					i = right;
					right = left;
					left = i;
				}

				/* test between top and bottom on the left hand side */
				for (y = top; *pi == *po && y <= bottom; y++) {
					offs = y * w;
					pi = &gif_in->picture[offs];
					po = &gif_out->picture[offs];
					for (x = 0; x < left; x++) {
						if (*pi++ != *po++) {
							left = x;
							break;
						}
					}
				}

				/* test between top and bottom on the right hand side */
				for (y = top; y <= bottom; y++) {
					offs = (y + 1) * w - 1;
					pi = &gif_in->picture[offs];
					po = &gif_out->picture[offs];
					for (x = w - 1; x > right; x--) {
						if (*pi-- != *po--) {
							right = x;
							break;
						}
					}
				}

				gif_in->img.left = left;
				gif_in->img.top = top;
				gif_in->img.width = right + 1 - left;
				gif_in->img.height = bottom + 1 - top;
				gif_out->img = gif_in->img;
				gif_write_gcl(gif_out);
				gif_write_image_header(gif_out);
				po = gif_out->picture;
				for (y = top; y <= bottom; y++) {
					pi = &gif_in->picture[y * w + left];
					for (x = left; x <= right; x++) {
						*po++ = *pi++;
					}
				}
				gif_encode(gif_out, gif_out->picture, gif_in->scr.bpp + 1,
					gif_in->img.width * gif_in->img.height);
				LOG(L_MINOR,("encoded w:%d, h:%d, l:%d, t:%d\n",
					gif_in->img.width, gif_in->img.height,
					gif_in->img.left, gif_in->img.top));

			} else {

				gif_out->img = gif_in->img;
				gif_write_gcl(gif_out);
				gif_write_image_header(gif_out);
				gif_out->img = gif_in->img;
				gif_encode(gif_out, gif_in->picture, gif_in->scr.bpp + 1,
					gif_in->img.width * gif_in->img.height);
				LOG(L_MINOR,("re-encoded w:%d, h:%d, l:%d, t:%d\n",
					gif_in->img.width, gif_in->img.height,
					gif_in->img.left, gif_in->img.top));
			}
		} else {
			/* pass thru */

			gif_out->img = gif_in->img;
			gif_write_gcl(gif_out);
			gif_write_image_header(gif_out);
			i = gif_getchar(gif_in);
			gif_putchar(gif_out, i);
			while ((i = gif_getchar(gif_in)) > 0) {
				gif_putchar(gif_out, i);
				while (i-- > 0)
					gif_putchar(gif_out, gif_getchar(gif_in));
			}
			gif_putchar(gif_out, 0);
		}
	}

	fclose(fp);
	return 0;
}

/* read an image from a GIF file */
gif_t *gif_read_file(const char *filename)
{
	gif_t *gif;
	FILE *fp;
	uint32_t i;
	size_t size;
	FUN("gif_read_file");

	gif = (gif_t *)xcalloc(1, sizeof(gif_t));

	if (NULL == (fp = fopen(filename, "rb"))) {
		LOG(L_ERROR,("fopen('%s','%s') call failed (%s)\n",
			filename, "rb", strerror(errno)));
		return NULL;
	}
	gif->user = fp;
	gif->input = gif_read_fp;

	gif_read_screen_header(gif, NULL, 0);

	for (;;) {
		i = gif_getchar(gif);
		switch (i) {
		case ',':
		case '\0':
			break;
		case '!':
			/* skip gif extension(s) */
			for (i = gif_getchar(gif); i > 0; i--)
				gif_getchar(gif);
			while ((i = gif_getchar(gif)) > 0) {
				for (/* */; i > 0; i--)
					gif_getchar(gif);
			}
			break;
		case ';':
			LOG(L_ERROR,("unexpected end-of-file\n"));
			fclose(fp);
			return NULL;
		default:
			fclose(fp);
			LOG(L_ERROR,("unexpected block type (%c)\n",
				i));
			return NULL;
		}

		if (i == ',')
			break;
	}

	for (i = 0; i < POW2(gif->scr.bpp); i++) {
		gif->gmap[i].cmap.red = gif->cmap[i].cmap.red;
		gif->gmap[i].cmap.green = gif->cmap[i].cmap.green;
		gif->gmap[i].cmap.blue = gif->cmap[i].cmap.blue;
	}

	gif_read_image_header(gif);

	size = gif->img.width * gif->img.height;
	gif->picture = (uint8_t *)xcalloc(size, sizeof(uint8_t));

	gif_decode(gif);

	return 0;
}

/* create a new GIF image width 'w', height 'h', bits per pixel 'bpp' ... */
gif_t *gif_create(int w, int h, int bpp, gif_color_t *palette,
	void *user, int (*output)(void *user, const void *buff, size_t size))
{
	gif_t *gif;
	uint32_t i;
	FUN("gif_create");

	if ((w > 0 && h > 0) && (bpp < 1 || bpp > 8)) {
		errno = EINVAL;
		return NULL;
	}

	gif = (gif_t *)xcalloc(1, sizeof(gif_t));

	gif->user = user;
	gif->output = output;

	if (w > 0 && h > 0 && bpp > 0) {
		gif->scr.width = w;
		gif->scr.height = h;
		gif->scr.bpp = bpp - 1;
		gif->scr.colormap = 1;

		gif->img.width = w;
		gif->img.height = h;
		gif->img.bpp = bpp - 1;

		if (NULL == palette) {
			/* set a default palette */
			for (i = 0; i < POW2(gif->scr.bpp); i++) {
				int n = i & 0x0f;
				int bg = 8 == (n & 0x08) ? 0x7f : 0x00;
				int fg = 7 == n  ? 0xbf : 0 == (n & 0x08) ? 0x7f : 0xff;

				gif->gmap[i].cmap.red = gif->cmap[i].cmap.red =
					(i & 0x01) ? fg : bg;
				gif->gmap[i].cmap.green = gif->cmap[i].cmap.green =
					(i & 0x02) ? fg : bg;
				gif->gmap[i].cmap.blue = gif->cmap[i].cmap.blue =
					(i & 0x04) ? fg : bg;
			}
		} else {
			memcpy(gif->gmap, palette,
				POW2(gif->scr.bpp) * sizeof(gif_color_t));
			memcpy(gif->cmap, palette,
				POW2(gif->scr.bpp) * sizeof(gif_color_t));
		}

		/* allocate picture buffer */
		gif->picture = (uint8_t *)xcalloc(w * h, sizeof(uint8_t));
	}

	return gif;
}

/* compute and send a delta frame to the GIF output stream in 'gif' */
int gif_delta(gif_t *gif)
{
	uint8_t xlt[256], *pi, *po;
	int top = 0, left = 0, bottom = 0, right = 0;
	int x, y, offs, l, h, w;
	uint32_t i, j;
	long int dsqmin, dsqcur;
	FUN("gif_delta");

	w = gif->img.width;
	h = gif->img.height;

	/* img is no longer interlaced */
	gif->img.interlace = 0;

	if (NULL != gif->old.picture) {
		if (gif->old.scr.bpp != gif->scr.bpp) {
			/* find best matching colors */
			for (j = 0; j < POW2(gif->scr.bpp); j++) {
				dsqmin = 256 * 256 * 3;
				for (i = 0; i < POW2(gif->old.scr.bpp); i++) {
					dsqcur =
						sq(gif->old.gmap[i].cmap.red,
							gif->cmap[j].cmap.red) +
						sq(gif->old.gmap[i].cmap.green,
							gif->cmap[j].cmap.green) +
						sq(gif->old.gmap[i].cmap.blue,
							gif->cmap[j].cmap.blue);
					if (dsqcur < dsqmin) {
						dsqmin = dsqcur;
						xlt[j] = i;
						if (0 == dsqmin)
							break;
					}
				}
			}

			LOG(L_MINOR,("translating gif frame #%d\n",
				gif->frame_count));
			for (l = 0; l < w * h; l++)
				gif->picture[l] = xlt[gif->picture[l]];
			gif->old.scr.bpp = gif->scr.bpp;
		}

		/* img does no longer have a colormap */
		gif->img.colormap = 0;
	}

	if (NULL != gif->old.picture &&
		gif->img.width == gif->old.img.width &&
		gif->img.height == gif->old.img.height &&
		gif->img.top == gif->old.img.top &&
		gif->img.left == gif->old.img.left) {

		pi = gif->picture;
		po = gif->old.picture;
		for (y = 0; y < h; y++) {
			for (x = 0; *pi == *po && x < w; x++) {
				pi++;
				po++;
			}
			/* row did not match? */
			if (x < w) {
				top = y;
				left = x;
				break;
			}
		}

		if (y != h) {
			/* image did not match */

			offs = w * h - 1;
			pi = &gif->picture[offs];
			po = &gif->old.picture[offs];
			for (y = h - 1; y >= top; y--) {
				for (x = w - 1; *pi == *po && x >= 0; x--) {
					pi--;
					po--;
				}
				if (x >= 0) {
					bottom = y + 1 < h - 1 ? y + 1 : y;
					right = x + 1 < w - 1 ? x + 1 : x;
					break;
				}
			}

			/* the outline of the difference may be slanted */
			if (right < left) {
				int tmp = right;
				right = left;
				left = tmp;
			}

			/* test between top and bottom on the left hand side */
			for (y = top; y <= bottom; y++) {
				offs = y * w;
				pi = &gif->picture[offs];
				po = &gif->old.picture[offs];
				for (x = 0; x < left; x++) {
					if (*pi++ != *po++) {
						left = x;
						break;
					}
				}
			}

			/* test between top and bottom on the right hand side */
			for (y = top; y <= bottom; y++) {
				offs = (y + 1) * w - 1;
				pi = &gif->picture[offs];
				po = &gif->old.picture[offs];
				for (x = w - 1; x > right; x--) {
					if (*pi-- != *po--) {
						right = x;
						break;
					}
				}
			}

			gif->img.left = left;
			gif->img.top = top;
			gif->img.width = right + 1 - left;
			gif->img.height = bottom + 1 - top;

			/* copy the rectangle to the old.picture */
			po = gif->old.picture;
			for (y = top; y <= bottom; y++) {
				offs = y * w + left;
				pi = &gif->picture[offs];
				for (x = left; x <= right; x++) {
					*po++ = *pi++;
				}
			}

			LOG(L_MINOR,("encode %d,%d %d,%d\n",
				gif->img.left, gif->img.top,
				gif->img.width, gif->img.height));
			gif_write_gcl(gif);
			gif_write_image_header(gif);
			gif_encode(gif, gif->old.picture, gif->scr.bpp + 1,
				gif->img.width * gif->img.height);

			memcpy(gif->old.picture, gif->picture, w * h);
			gif->img = gif->old.img;

		} else {
			/* image was identical */

			gif->old.img = gif->img;
			LOG(L_MINOR,("recode %d,%d %d,%d\n",
				gif->img.left, gif->img.top,
				gif->img.width, gif->img.height));
			gif_write_gcl(gif);
			gif_write_image_header(gif);
			gif_encode(gif, gif->picture, gif->scr.bpp + 1,
				gif->img.width * gif->img.height);
		}

	} else {
		/* pass thru */
		LOG(L_MINOR,("first %d,%d %d,%d\n",
			gif->img.left, gif->img.top,
			gif->img.width, gif->img.height));

		if (NULL != gif->old.picture) {
			xfree(gif->old.picture);
			gif->old.picture = NULL;
		}
		gif->old.picture = (uint8_t *)xcalloc(w * h, sizeof(uint8_t));
		memcpy(gif->old.picture, gif->picture, w * h);
		memcpy(gif->old.gmap, gif->gmap, sizeof(gif->old.gmap));
		memcpy(gif->old.cmap, gif->cmap, sizeof(gif->old.cmap));
		gif->old.scr = gif->scr;
		gif->old.img = gif->img;
		gif->old.scr.colormap = 0;
		gif->old.img.colormap = 1;

		gif_write_image_header(gif);
		gif_encode(gif, gif->picture, gif->scr.bpp + 1,
			gif->img.width * gif->img.height);
	}
	gif_putchar(gif, 0);

	return 0;
}

/* finish changes to a GIF frame buffer and send (delta) image to output */
int gif_finish(gif_t *gif, int lastimage)
{
	int flags;
	uint32_t i;
	FUN("gif_finish");

	if (0 == gif->frame_count) {
		gif_putbuff(gif, "GIF89a", 6);

		/* screen header */
		gif_putshort(gif, gif->scr.width);
		gif_putshort(gif, gif->scr.height);

		flags = (gif->scr.colormap << 7) & 0x80;
		flags |= (gif->scr.cres << 4) & 0x70;
		flags |= (gif->scr.sorted << 3) & 0x08;
		flags |= gif->scr.bpp & 0x07;

		gif_putchar(gif, flags);
		gif_putchar(gif, gif->scr.bc);
		gif_putchar(gif, gif->scr.aspect);
		if (0 != gif->scr.colormap) {
			for (i = 0; i < POW2(gif->scr.bpp); i++) {
				gif_putchar(gif, gif->gmap[i].cmap.red);
				gif_putchar(gif, gif->gmap[i].cmap.green);
				gif_putchar(gif, gif->gmap[i].cmap.blue);
			}
			LOG(L_MINOR,("global color map w/ %d entries for frame #%d\n",
				i, gif->frame_count));
		}
		if (gif->loop > 0) {
			gif_loop(gif, gif->loop);
		}
	}

	gif->disposal = DISP_NONE;

	gif_delta(gif);

	if (0 == lastimage) {
		gif->frame_count += 1;
	} else {
		gif_putchar(gif, ';');

		xfree(gif->old.picture);
		xfree(gif->picture);
		xfree(gif);
	}

	return 0;
}

/* move region inside gif */
int gif_move(gif_t *gif, int xd, int yd, int xs, int ys, int w, int h)
{
	int y;
	uint32_t dst, src;
	FUN("gif_move");

	if (yd < ys) {
		for (y = 0; y < h; y++) {
			if (y + yd < 0 || y + yd > gif->img.height)
				continue;
			dst = gif->img.width * (yd + y) + xd;
			if (y + ys < 0 || y + ys > gif->img.height) {
				memset(&gif->picture[dst], 0, w);
			} else {
				src = gif->img.width * (ys + y) + xs;
				memmove(&gif->picture[dst], &gif->picture[src], w);
			}
		}
	} else {
		for (y = h - 1; y >= 0; y--) {
			if (y + yd < 0 || y + yd > gif->img.height)
				continue;
			dst = gif->img.width * (yd + y) + xd;
			if (y + ys < 0 || y + ys > gif->img.height) {
				memset(&gif->picture[dst], 0, w);
			} else {
				src = gif->img.width * (ys + y) + xs;
				memmove(&gif->picture[dst], &gif->picture[src], w);
			}
		}
	}

	return 0;
}

/* bit block transfer */
int gif_blit(gif_t *gif, int xd, int yd, int w, int h,
	uint32_t *ppix, int rowsize, int fg, int bg, int mode)
{
	static const uint32_t bits[] = {
		0x00000080, 0x00000040, 0x00000020, 0x00000010,
		0x00000008, 0x00000004, 0x00000002, 0x00000001,
		0x00008000, 0x00004000, 0x00002000, 0x00001000,
		0x00000800, 0x00000400, 0x00000200, 0x00000100,
		0x00800000, 0x00400000, 0x00200000, 0x00100000,
		0x00080000, 0x00040000, 0x00020000, 0x00010000,
		0x80000000, 0x40000000, 0x20000000, 0x10000000,
		0x08000000, 0x04000000, 0x02000000, 0x01000000
	};
	int x, y;
	uint32_t offs, pix = 0;
	FUN("gif_blit");

	for (y = yd; y < yd + h; y++, ppix += rowsize) {
		/* y outside the picture? */
		if (y < 0 || y >= gif->scr.height)
			continue;
		/* left offset in this row */
		offs = gif->img.width * y + xd;
		for (x = 0; x < w; x++) {
			if (0 == (x % 32)) {
				pix = ppix[x / 32];
			}
			if (x + xd < 0 || x + xd >= gif->img.width)
				continue;
			if (0 != (pix & bits[x % 32])) {
				gif->picture[offs + x] = fg;
			} else if (mode == GIF_BLITCOPY) {
				gif->picture[offs + x] = bg;
			}
		}
	}

	return 0;
}

/* set a palette entry */
int gif_set_pal(gif_t *gif, int color, gif_color_t *rgb)
{
	if (color >= (int)POW2(gif->scr.bpp)) {
		errno = EINVAL;
		return -1;
	}
	/* no change? */
	if (0 == memcmp(&gif->cmap[color], rgb, sizeof(*rgb))) {
		return 0;
	}
	if (0 == gif->img.colormap) {
		memcpy(gif->cmap, gif->gmap, POW2(gif->scr.bpp) * sizeof(gif_color_t));
		gif->img.colormap = 1;
	}
	gif->cmap[color] = *rgb;

	return 0;
}

/* put a pixel in the GIF frame buffer */
int gif_put_pixel(gif_t *gif, int x, int y, int color)
{
	uint32_t offs;
	FUN("gif_put_pixel");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}

	if (x < 0 || x >= gif->img.width ||
		y < 0 || y >= gif->img.height) {
		errno = EINVAL;
		return -1;
	}

	offs = gif->img.width * y + x;
	gif->picture[offs] = color;

	return 0;
}

/* draw a horizontal line in the GIF frame buffer */
int gif_hline(gif_t *gif, int x, int y, int w, uint8_t pattern, int color)
{
	uint32_t offs;
	int i;
	FUN("gif_hline");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}

	if (x < 0 || x >= gif->img.width ||
		y < 0 || y >= gif->img.height) {
		errno = EINVAL;
		return -1;
	}

	offs = gif->img.width * y + x;
	if (x + w > gif->img.width)
		w = gif->img.width - x;
	if (0xff == pattern) {
		memset(&gif->picture[offs], color, w);
	} else {
		for (i = 0; i < w; i++, offs++) {
			if (pattern & (0x80 >> (i % 8)))
				gif->picture[offs] = color;
		}
	}

	return 0;
}

/* draw a vertical line in the GIF frame buffer */
int gif_vline(gif_t *gif, int x, int y, int h, uint8_t pattern, int color)
{
	uint32_t offs;
	int i;
	FUN("gif_vline");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}

	if (x < 0 || x >= gif->img.width ||
		y < 0 || y >= gif->img.height) {
		errno = EINVAL;
		return -1;
	}

	offs = gif->img.width * y + x;
	if (y + h > gif->img.height)
		h = gif->img.height - y;
	for (i = 0; i < h; i++, offs += gif->img.width) {
		if (pattern & (0x80 >> (i % 8)))
			gif->picture[offs] = color;
	}

	return 0;
}

/* draw a rectangle in the GIF frame buffer */
int gif_rectangle(gif_t *gif, int x0, int y0, int x1, int y1, int color)
{
	int w, h;
	FUN("gif_rectangle");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}
	w = x1 + 1 - x0;
	h = y1 + 1 - y0;
	gif_hline(gif, x0, y0, w, 0xff, color);
	gif_hline(gif, x0, y1, w, 0xff, color);
	gif_vline(gif, x0, y0, h, 0xff, color);
	gif_vline(gif, x1, y0, h, 0xff, color);

	return 0;
}

/* draw a 3D rectangle in the GIF frame buffer */
int gif_3d_rectangle(gif_t *gif, int x0, int y0, int x1, int y1,
	int topleft_color, int bottomright_color, int thickness)
{
	int i, w, h;
	FUN("gif_3d_rectangle");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}
	w = x1 + 1 - x0;
	h = y1 + 1 - y0;
	for (i = 0; i < thickness; i++) {
		gif_hline(gif, x0+i, y0+i, w-2*i, 0xff, topleft_color);
		gif_vline(gif, x0+i, y0+i, h-2*i, 0xff, topleft_color);
		gif_hline(gif, x0+i, y1-i, w-2*i, 0xff, bottomright_color);
		gif_vline(gif, x1-i, y0+i, h-2*i, 0xff, bottomright_color);
	}

	return 0;
}

/* draw a filled rectangle in the GIF frame buffer */
int gif_filled_rectangle(gif_t *gif, int x0, int y0, int x1, int y1, int color)
{
	int w, y;
	FUN("gif_rectangle");

	if (NULL == gif || NULL == gif->picture) {
		errno = EINVAL;
		return -1;
	}

	w = x1 + 1 - x0;
	for (y = y0; y <= y1; y++)
		gif_hline(gif, x0, y, w, 0xff, color);

	return 0;
}

/* print a character (using font8x13 data) into the GIF framebuffer */
int gif_printch(gif_t *gif, int x0, int y0, int color, int ch)
{
	uint8_t *font;
	int y;
	FUN("gif_printch");

	if (NULL == font8x13) {
		return -1;
	}
	if (ch < 0 || ch > 255) {
		errno = EINVAL;
		return -1;
	}

	font = &font8x13[13 * ch];
	for (y = 0; y < 13; y++, font++) {
		gif_hline(gif, x0, y0 + y, 8, *font, color);
	}

	return 0;
}

/* print a formatted string into the GIF framebuffer */
int gif_printf(gif_t *gif, int x, int y, int color, const char *fmt, ...)
{
	char *buff = NULL, *src;
	int x0 = x;
	size_t len;
	va_list ap;
	FUN("gif_printf");

	buff = xcalloc(1024, sizeof(char));
	va_start(ap, fmt);
	len = pm_vsnprintf(buff, 1024, fmt, ap);
	va_end(ap);

	/* very simple string print character loop */
	for (src = buff; *src; src++) {
		switch (*src) {
		case '\b':
			x -= 8;
			break;
		case '\r':
			x = x0;
			break;
		case '\n':
			y += 13;
			break;
		default:
			gif_printch(gif, x, y, color, *src);
			x += 8;
		}
	}
	return len;
}

/* read a GIF screen header and also send a copy to gif_out if non-NULL */
int gif_read_screen_header(gif_t *gif_in, gif_t *gif_out, int firstimage)
{
	int temp, flags;
	uint32_t i;
	size_t size;
	FUN("gif_read_screen_header");

	for (i = 0; i < 6; i++) {
		temp = gif_getchar(gif_in);
		if (4 == i && '7' == temp)
			temp = '9';
		if (NULL != gif_out && 0 != firstimage) {
			gif_putchar(gif_out, temp);
		}
	}

	gif_in->scr.width = gif_getshort(gif_in);
	gif_in->scr.height = gif_getshort(gif_in);
	flags = gif_getchar(gif_in);
	if (NULL != gif_out && 0 != firstimage) {
		gif_out->scr.width = gif_in->scr.width;
		gif_out->scr.height = gif_in->scr.height;
		gif_putshort(gif_out, gif_out->scr.width);
		gif_putshort(gif_out, gif_out->scr.height);
		gif_putchar(gif_out, flags);
	}

	/* colormap flag */
	gif_in->scr.colormap = (flags & 0x80) >> 7;

	/* color resolution */
	gif_in->scr.cres = (flags & 0x70) >> 4;

	/* sorted palette flag */
	gif_in->scr.sorted = (flags & 0x08) >> 3;

	/* bits per pixel */
	gif_in->scr.bpp = flags & 0x07;

	/* background color */
	gif_in->scr.bc = gif_getchar(gif_in);
	if (NULL != gif_out && 0 != firstimage) {
		gif_out->scr.bc = gif_in->scr.bc;
		gif_putchar(gif_out, gif_out->scr.bc);

		if (0 == gif_in->scr.bc) {
			/* fill a pseudo screen with the background color */
			size = gif_in->scr.width * gif_in->scr.height;
			gif_out->old.img.top = 0;
			gif_out->old.img.left = 0;
			gif_out->old.img.width = gif_in->scr.width;
			gif_out->old.img.height = gif_in->scr.height;
			gif_out->old.img.bpp = gif_in->img.bpp;
			gif_out->old.picture = (uint8_t *)xcalloc(size, sizeof(uint8_t));
		}
	}


	LOG(L_DEBUG,("screen #%d: %dx%dx%d colormap:%d cres:%d bc:%d bpp:%d\n",
		gif_in->frame_count,
		gif_in->scr.width,
		gif_in->scr.height,
		POW2(gif_in->scr.bpp),
		gif_in->scr.colormap,
		gif_in->scr.cres,
		gif_in->scr.bc,
		gif_in->scr.bpp));

	/* have a global color map? */	
	if (0 != gif_in->scr.colormap) {
		for (i = 0; i < POW2(gif_in->scr.bpp); i++) {
			gif_in->gmap[i].cmap.red = gif_in->cmap[i].cmap.red =
				gif_getchar(gif_in);
			gif_in->gmap[i].cmap.green = gif_in->cmap[i].cmap.green =
				gif_getchar(gif_in);
			gif_in->gmap[i].cmap.blue = gif_in->cmap[i].cmap.blue =
				gif_getchar(gif_in);

			if (NULL != gif_out && 0 != firstimage) {
				gif_putchar(gif_out, gif_in->gmap[i].cmap.red);
				gif_putchar(gif_out, gif_in->gmap[i].cmap.green);
				gif_putchar(gif_out, gif_in->gmap[i].cmap.blue);
				if (TRANS_RGB == gif_out->trans.type &&
					0 == gif_out->trans.valid) {
					if (gif_out->trans.red == gif_in->gmap[i].cmap.red &&
						gif_out->trans.green == gif_in->gmap[i].cmap.green &&
						gif_out->trans.blue == gif_in->gmap[i].cmap.blue) {
						LOG(L_DEBUG,("trans match at %d\n", i));
						gif_out->trans.map = i;
						gif_out->trans.valid = 1;
					} else {
						LOG(L_DEBUG,("non trans RGB %d: %x,%x,%x\n",
							i,
							gif_in->gmap[i].cmap.red, 
							gif_in->gmap[i].cmap.green, 
							gif_in->gmap[i].cmap.blue));
					}
				}
			}
		}
	}

	return 0;
}

/* read a GIF image header */
int gif_read_image_header(gif_t *gif)
{
	int flag;
	uint32_t i;
	FUN("gif_read_image_header");

	gif->img.left = gif_getshort(gif);
	gif->img.left += gif->left;

	gif->img.top = gif_getshort(gif);
	gif->img.top += gif->top;

	gif->img.width = gif_getshort(gif);
	gif->img.height = gif_getshort(gif);

	flag = gif_getchar(gif);
	gif->img.colormap = (flag & 0x80) >> 7;
	gif->img.interlace = (flag & 0x40) >> 6;
	gif->img.sorted = (flag & 0x20) >> 5;
	gif->img.bpp = flag & 0x07;

	LOG(L_DEBUG,("image %dx%dx%d (%d,%d) colormap:%d, i:%d, bpp:%d\n",
		gif->img.width, gif->img.height, POW2(gif->img.bpp),
		gif->img.left, gif->img.top,
		gif->img.colormap, gif->img.interlace, gif->img.bpp));

	if (0 != gif->img.colormap) {
		LOG(L_MINOR,("transferring colormap w/ %d colors\n",
			POW2(gif->img.bpp)));
		gif->scr.bpp = gif->img.bpp;
		for (i = 0; i < POW2(gif->scr.bpp); i++) {
			gif->cmap[i].cmap.red = gif_getchar(gif);
			gif->cmap[i].cmap.green = gif_getchar(gif);
			gif->cmap[i].cmap.blue = gif_getchar(gif);
		}
	}

	if (0 != gif->scr.colormap) {
		/* assume that we won't need a local color map */
		gif->img.colormap = 0;
		for (i = 0; i < POW2(gif->scr.bpp); i++) {
			if (gif->gmap[i].cmap.red != gif->cmap[i].cmap.red ||
				gif->gmap[i].cmap.green != gif->cmap[i].cmap.green ||
				gif->gmap[i].cmap.blue != gif->cmap[i].cmap.blue) {
				gif->img.colormap = 1;
				break;
			}
		}
	} else {
		/* we need a local color map */
		gif->img.colormap = 1;
	}

	return 0;
}

/* compute and write a GIF GCL to the output stream */
int gif_write_gcl(gif_t *gif)
{
	int flags, trans;
	uint32_t i;
	FUN("gif_write_gcl");

	/* compute a GIF GCL */

	gif_putchar(gif, '!');
	gif_putchar(gif, 0xf9);
	gif_putchar(gif, 0x04);

	flags = gif->disposal << 2;
	if (0 != gif->time) {
		flags |= 0x80;
	}
	if (TRANS_RGB == gif->trans.type && 0 == gif->trans.valid) {
		gif->img.colormap = 1;
	}

	trans = gif->trans.map;
	if (0 != gif->img.colormap && TRANS_RGB == gif->trans.type) {
		trans = 0;
		for (i = 0; i < POW2(gif->scr.bpp); i++) {
			if (gif->trans.red == gif->cmap[i].cmap.red &&
				gif->trans.green == gif->cmap[i].cmap.green &&
				gif->trans.blue == gif->cmap[i].cmap.blue) {
				LOG(L_DEBUG,("transparent color #%d\n", i));
				trans = i;
				flags |= 0x01;
			}
		}
	} else if (0 != gif->trans.valid) {
		flags |= 0x01;
	}

	gif_putchar(gif, flags);

	/* delay speed: 0 is instantaneous */
	gif_putshort(gif, gif->time);

	/* transparency color index */
	gif_putchar(gif, trans);
	if (flags & 0x01) {
		LOG(L_MINOR,("transparent color:%d\n", trans));
	}
	LOG(L_MINOR,("GCL delay:%d\n", gif->time));

	gif_putchar(gif, 0);
	/* end of GIF GCL */

	return 0;
}

/* write a GIF image header to the output stream */
int gif_write_image_header(gif_t *gif)
{
	int flags;
	uint32_t i;
	FUN("gif_write_image_hdr");

	gif_putchar(gif, ',');
	gif_putshort(gif, gif->img.left);
	gif_putshort(gif, gif->img.top);
	gif_putshort(gif, gif->img.width);
	gif_putshort(gif, gif->img.height);

	flags = (gif->img.colormap << 7) & 0x80;
	flags |= (gif->img.interlace << 6) & 0x40;
	flags |= (gif->img.sorted << 5) & 0x20;
	flags |= gif->scr.bpp & 0x07;
	gif_putchar(gif, flags);

	if (0 != gif->img.colormap) {
		for (i = 0; i < POW2(gif->img.bpp); i++) {
			gif_putchar(gif, gif->cmap[i].cmap.red);
			gif_putchar(gif, gif->cmap[i].cmap.green);
			gif_putchar(gif, gif->cmap[i].cmap.blue);
		}
		LOG(L_MINOR,("local color map w/ %d entries for frame #%d\n",
			i, gif->frame_count));
	}

	return 0;
}

/* write a GIF comment to the output stream */
int gif_comment(gif_t *gif, const char *src)
{
	int len = strlen(src);
	FUN("gif_comment");

	if (len > 254) {
		LOG(L_ERROR,("comment string too long (%d)\n",
			len));
		errno = EINVAL;
		return -1;
	} else if (len > 0) {
		gif_putchar(gif, '!');
		gif_putchar(gif, 0xfe);
		gif_putchar(gif, len);
		gif_putbuff(gif, src, len);
		gif_putchar(gif, 0);
	}

	return 0;
}

/* write a GIF loop extension to the output stream */
int gif_loop(gif_t *gif, int loop)
{
	static const char ns2[] = "NETSCAPE2.0";
	int len = strlen(ns2);
	FUN("gif_comment");

	gif_putchar(gif, '!');
	gif_putchar(gif, 0xff);
	gif_putchar(gif, len);
	gif_putbuff(gif, ns2, len);
	gif_putchar(gif, 0x03);
	gif_putchar(gif, 0x01);
	gif_putshort(gif, loop);	/* loop count */
	gif_putchar(gif, 0);

	return 0;
}

#undef	LINESIZE
#define	LINESIZE	64

/* GIF initializations */
int gif(void)
{
	char *filename = NULL;
	size_t len = strlen(g_conf->configpath) + strlen(FONTNAME) + 1;
	char *line = NULL;
	FILE *fp;
	size_t offs, size;
	FUN("gif");

	filename = xcalloc(len, sizeof(char));
	pm_snprintf(filename, len, "%s%s",
		g_conf->configpath, FONTNAME);
	LOG(L_MINOR,("loading font %s\n", filename));
	fp = fopen(filename, "r");
	if (NULL == fp) {
		LOG(L_ERROR,("could not open font %s\n",
			filename));
		info("%s missing ", FONTNAME);
		return -1;
	}

	/* size of the font after decoding */
	size = 256 * 13;
	font8x13 = (uint8_t *)xcalloc(size, sizeof(uint8_t));

	line = xcalloc(LINESIZE, sizeof(char));

	offs = 0;
	while (!feof(fp)) {
		uint8_t bits;
		if (NULL == fgets(line, LINESIZE, fp))
			break;
		if (*line == '\n')
			continue;
		if (*line == '#') {
			offs = 13 * strtoul(line + 2, NULL, 0);
			if (offs > size) {
				LOG(L_ERROR,("invalid character number '%s'\n",
					line));
				return -1;
			}
		}
		bits = 0;
		if ('x' == line[0]) bits |= 0x80;
		if ('x' == line[1]) bits |= 0x40;
		if ('x' == line[2]) bits |= 0x20;
		if ('x' == line[3]) bits |= 0x10;
		if ('x' == line[4]) bits |= 0x08;
		if ('x' == line[5]) bits |= 0x04;
		if ('x' == line[6]) bits |= 0x02;
		if ('x' == line[7]) bits |= 0x01;
		font8x13[offs] = bits;
		if (++offs >= size)
			break;
	}
	fclose(fp);
	info("%s ", FONTNAME);

	return 0;
}

